package com.boboPlanet.user.service.impl;

import com.alibaba.fastjson.JSON;
import com.boboPlanet.comm.constance.BaseDictConfigEnum;
import com.boboPlanet.comm.context.BusinessStartTimeContext;
import com.boboPlanet.comm.context.BusinessThreadContextHolder;
import com.boboPlanet.comm.context.LoginUserContext;
import com.boboPlanet.comm.entity.dto.LoginUserInfoDTO;
import com.boboPlanet.comm.entity.po.LoginDetailInfoPO;
import com.boboPlanet.comm.entity.po.LoginUserInfoPO;
import com.boboPlanet.comm.entity.po.SysDictConfigPO;
import com.boboPlanet.comm.entity.vo.LoginResultVO;
import com.boboPlanet.comm.enums.LoginUserErrorEnum;
import com.boboPlanet.comm.enums.TimeUnitEnum;
import com.boboPlanet.comm.exception.BusinessException;
import com.boboPlanet.comm.result.ResponseResult;
import com.boboPlanet.comm.utils.AgeConvertUtil;
import com.boboPlanet.comm.utils.AutoGenerateIds;
import com.boboPlanet.comm.component.JwtConfig;
import com.boboPlanet.comm.utils.MD5PwdUtils;
import com.boboPlanet.user.async.AsyncTask;
import com.boboPlanet.user.component.PlanetUserAccountLoginEvent;
import com.boboPlanet.user.component.PlanetUserEmailLoginEvent;
import com.boboPlanet.user.component.PlanetUserSmsLoginEvent;
import com.boboPlanet.user.entity.dto.ArticleMainDTO;
import com.boboPlanet.user.entity.dto.SysLogDTO;
import com.boboPlanet.user.entity.dto.UpdateUserInfoDTO;
import com.boboPlanet.user.entity.po.RolePO;
import com.boboPlanet.user.entity.vo.CurrentUserInfoVO;
import com.boboPlanet.user.entity.vo.UserArtListVO;
import com.boboPlanet.user.entity.vo.UserArtMainVO;
import com.boboPlanet.user.enums.RoleEnumDict;
import com.boboPlanet.user.enums.SysOperatorEnum;
import com.boboPlanet.user.feignApi.BaseDictConfigApi;
import com.boboPlanet.user.mapper.*;
import com.boboPlanet.user.service.UserCheckService;
import com.boboPlanet.user.service.UserOperationService;
import com.boboPlanet.user.single.SingleFactory;
import com.boboPlanet.user.enums.FileTypeEnum;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @Description: TODO
 * @Author: mis_wu
 * @Date: 2023/2/14
 **/
@Service
@Slf4j
public class UserOperationServiceImpl implements UserOperationService {

    private static final List<String> SEX_LIST = Arrays.asList("男","女");

    private static final String TEL_REG = "((\\d{11})|^((\\d{7,8})|(\\d{4}|\\d{3})-(\\d{7,8})|(\\d{4}|\\d{3})-(\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1})|(\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1}))$)";

    private static final String AGE_REG = "^(?:[1-9][0-9]?|1[01][0-9]|120)$";

    private static final String YEAR = "岁";

    /**
     * 默认登录过期时间 1h
     */
    private static final Integer DEFAULT_EXPIRE_TIME = 1;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private UserOperationMapper userOperationMapper;

    @Resource
    private UserCheckService userCheckService;

    @Resource
    private UserDetailOperationMapper userDetailOperationMapper;

    @Resource
    private BaseDictConfigApi baseDictConfigApi;

    @Resource
    private AsyncTask asyncTask;

    @Resource
    private ArticleMapper articleMapper;

    @Resource
    private FileUploadServiceFactory fileUploadServiceFactory;

    @Resource
    private MenuMapper menuMapper;

    @Resource
    private JwtConfig jwtConfig;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult<Object> planetLogin(LoginUserInfoDTO dto) {
        //todo: 选择登录方式(发布事件)
        if ("email".equals(dto.getLoginMode())){
            applicationEventPublisher.publishEvent(new PlanetUserEmailLoginEvent(this,dto.getEmail()));
        } else if ("phone".equals(dto.getLoginMode())) {
            applicationEventPublisher.publishEvent(new PlanetUserSmsLoginEvent(this,dto.getPhone()));
        }
        LoginResultVO resVo;
        if (ObjectUtils.isEmpty(dto) || StringUtils.isBlank(dto.getLoginAccount()) || StringUtils.isBlank(dto.getLoginPwd())){
            resVo = new LoginResultVO(false,null,null);
            return ResponseResult.error(LoginUserErrorEnum.USER_LOGIN_INFO_NOT_NULL,resVo, (String) null);
        }

        LoginUserInfoPO loginUserInfo = userOperationMapper.queryLoginUserInfoByAccount(dto.getLoginAccount());
        ResponseResult<Object> check = userCheckService.loginCheck(loginUserInfo, LoginUserErrorEnum.USER_INFO_NOT_EXIST);
        if (!check.getSuccess()){
            return check;
        }

        //todo: 判断登录状态 避免重复登录
        if (loginUserInfo.getAccountState() == 1){
            throw BusinessException.build(LoginUserErrorEnum.CURRENT_ACCOUNT_EXCEPTION.getDesc());
        }
        Map<Object, Object> keyMap = stringRedisTemplate.opsForHash().entries(loginUserInfo.getLoginToken());
        if (loginUserInfo.getLoginState() == 0 && !keyMap.isEmpty()){
            throw BusinessException.build(LoginUserErrorEnum.CURRENT_ACCOUNT_IS_LOGIN.getDesc());
        }

        ResponseResult<SysDictConfigPO> dictConfigRes = baseDictConfigApi.getOneDictConfigByCode(BaseDictConfigEnum.login_fail_max_count.name());
        log.info("登录获取最大错误登录次数配置:{}",dictConfigRes);
        if (!dictConfigRes.getSuccess() && ObjectUtils.isEmpty(dictConfigRes.getData())){
            throw BusinessException.build("获取基础配置失败,请联系管理员!!!");
        }
        String configValue = dictConfigRes.getData().getConfigValue();
        if (configValue == null){
            configValue = dictConfigRes.getData().getConfigDefault();
        }
        if (loginUserInfo.getLoginFailedCount() > Integer.parseInt(configValue)){
            userOperationMapper.updateAccountState(loginUserInfo.getAccountId(),1);
            return ResponseResult.error(LoginUserErrorEnum.CURRENT_ACCOUNT_IS_LOCK,null);
        }

        String encPwd = MD5PwdUtils.encryption(dto.getLoginPwd());
        if (!Objects.equals(encPwd,loginUserInfo.getLoginPwd())){
            userOperationMapper.updateLoginFailCount(loginUserInfo.getAccountId());
            return ResponseResult.error(LoginUserErrorEnum.USER_NAME_PASSWORD_ERROR,null,"");
        }
        loginUserInfo.setUserName(userOperationMapper.getUserNameById(loginUserInfo.getUserId()));
        LoginUserInfoDTO userDto = UserInfoConvert.INSTANCE.po2dto(loginUserInfo);
        userDto.setLoginPwd(StringUtils.EMPTY);
        String sign = jwtConfig.createToken(JSON.toJSONString(userDto), Long.valueOf(dto.getLoginValidityDay()==null?DEFAULT_EXPIRE_TIME:dto.getLoginValidityDay()), TimeUnitEnum.HOURS.getUnit());
        if (StringUtils.isBlank(sign)){
            return ResponseResult.error("生成用户签名失败,请联系管理员!!!");
        }
        String token = UUID.randomUUID().toString().replaceAll("-","");
        userDto.setToken(token);
        resVo = new LoginResultVO(true,sign,userDto);

        loginUserInfo.setLoginValidityDay(dto.getLoginValidityDay());

        LoginUserContext.mock(loginUserInfo);

        BusinessThreadContextHolder capture = BusinessThreadContextHolder.capture();

        //异步缓存用户信息
        asyncTask.tranceLog(SingleFactory.getLogDto(SysOperatorEnum.USER_LOGIN.getDesc()));
        try {
            asyncTask.loginTaskExecute(loginUserInfo, token,capture);
        }catch (Exception e){
            log.error("login error ==> "+e.getMessage(),e);
        }

        return ResponseResult.isSuccess(resVo);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult<Boolean> planetRegister(LoginUserInfoDTO dto) {
        if (ObjectUtils.isEmpty(dto) || StringUtils.isBlank(dto.getLoginAccount()) || StringUtils.isBlank(dto.getLoginPwd())){
            return ResponseResult.error(LoginUserErrorEnum.USER_REG_INFO_NOT_NULL,false);
        }
        LoginUserInfoPO loginUserInfo = userOperationMapper.queryLoginUserInfoByAccount(dto.getLoginAccount());
        if (ObjectUtils.isNotEmpty(loginUserInfo)){
            return ResponseResult.error(LoginUserErrorEnum.USER_REG_INFO_IS_EXIST,false);
        }
        // todo: 正则验证账号密码
        String accountReg = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
        String pwdReg = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{6,10}$";

        boolean accountBool = Pattern.matches(accountReg,dto.getLoginAccount());
        boolean pwdBool = Pattern.matches(pwdReg,dto.getLoginPwd());
        if (!accountBool || !pwdBool){
            throw BusinessException.build("账号/密码不符合规范,请重新注册!!!");
        }

        try {
            //用户明细数据
            LoginDetailInfoPO detailPo = new LoginDetailInfoPO();
            detailPo.setUserId(AutoGenerateIds.getGenerateId());
            detailPo.setUserName("无");
            detailPo.setUserSex("无");
            detailPo.setUserAge("无");
            detailPo.setUserAgeHour(1L);
            detailPo.setUserAgeUnit("小时");
            detailPo.setUserPhone("无");
            detailPo.setUserAddress("无");
            detailPo.setHobbies("无");
            detailPo.setUserDescribe("无");
            //设置默认头像
            detailPo.setUserAvatarUrl("D:/Projects/proImg/planetImg/1212121_121212.webp");
            detailPo.setAvatarName("1212121_121212.webp");
            boolean result = userDetailOperationMapper.insert(detailPo)>0;
            if (!result){
                throw BusinessException.build("注册失败,请联系管理员!!!");
            }
            //用户账号数据
            LoginUserInfoPO currUser = new LoginUserInfoPO();
            currUser.setAccountId(AutoGenerateIds.getGenerateId());
            currUser.setUserId(detailPo.getUserId());
            currUser.setLoginAccount(dto.getLoginAccount());
            currUser.setLoginPwd(MD5PwdUtils.encryption(dto.getLoginPwd()));
            currUser.setLoginValidityDay(1);
            currUser.setLoginToken("reg-test-token");
            currUser.setLoginMode(0);
            currUser.setRegTime(BusinessStartTimeContext.getTime());
            result = userOperationMapper.insert(currUser) > 0;
            if (!result){
                throw BusinessException.build("注册失败,请联系管理员!!!");
            }
            //分配角色
            List<RolePO> roleList = menuMapper.getRoleList(RoleEnumDict.normal_user.getRoleName(), BaseDictConfigEnum.bobo_planet.name(), true, false);
            if (roleList == null || roleList.size() != 1){
                throw BusinessException.build("角色分配失败,请联系管理员维护正确的角色信息!!!");
            }
            result = menuMapper.distributionRole(AutoGenerateIds.getGenerateId(),detailPo.getUserId(), roleList.get(0).getRoleId()) > 0;
            if (!result){
                throw BusinessException.build("注册分配角色失败,请联系管理员!!!");
            }
            SysLogDTO logDto = new SysLogDTO();
            logDto.setLogUser(currUser.getLoginAccount());
            logDto.setLogContent(SysOperatorEnum.USER_REGISTER.getDesc()+",账户id:"+currUser.getAccountId());
            logDto.setLogTime(BusinessStartTimeContext.getTime());
            logDto.setLogSys(BaseDictConfigEnum.bobo_planet.name());
            asyncTask.tranceLog(logDto);
        }catch (Exception e){
            log.error("用户注册失败:"+e.getMessage(),e);
            throw BusinessException.build("用户注册失败," + e.getMessage());
        }

        return ResponseResult.isSuccess(true);
    }


    @Override
    public Boolean planetUserLogout(String account, String token) {

        if (account == null || StringUtils.isBlank(token)){
            return false;
        }

        Object userObj = stringRedisTemplate.opsForHash().get(token, account);

        LoginUserInfoPO userInfo = JSON.parseObject((String) userObj,LoginUserInfoPO.class);

        if (ObjectUtils.isEmpty(userInfo)){
            log.error("planet user logout fail,get redis cache fail,the token is: {}, account is: {}",token,account);
            return false;
        }

        //清空账户信息
        userOperationMapper.clearUserLoginState(userInfo.getAccountId());

        stringRedisTemplate.opsForHash().delete(token,account);

        SysLogDTO dto = new SysLogDTO();
        dto.setLogUser(userInfo.getUserName());
        dto.setLogContent(SysOperatorEnum.USER_LOGOUT.getDesc()+",账户id:"+userInfo.getAccountId());
        dto.setLogTime(BusinessStartTimeContext.getTime());
        dto.setLogSys(BaseDictConfigEnum.bobo_planet.name());
        asyncTask.tranceLog(dto);

        BusinessThreadContextHolder.clear();

        return true;
    }

    /**
     * 修改 planet user info
     * @param dto UpdateUserInfoDTO
     * @return Boolean
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult<Boolean> updatePlanetUserInfo(UpdateUserInfoDTO dto) {
        log.info("update user info params: {}",dto);
        if (ObjectUtils.isEmpty(dto)){
            return ResponseResult.error(null,"参数校验错误",false);
        }

        //un todo:没有需要的更新主信息
        //userOperationMapper.updateAccountInfoById(dto);

        //todo:年龄换算
        boolean ageRes = Pattern.matches(AGE_REG, String.valueOf(dto.getUserAge()));
        if (!ageRes){
            throw BusinessException.build("【年龄】修改错误,不合法，只允许1-120之间...");
        }
        if (!SEX_LIST.contains(dto.getUserSex())){
            throw BusinessException.build("性别修改错误,只允许 男/女...");
        }
        boolean phone = Pattern.matches(TEL_REG, dto.getUserPhone());
        if (!phone){
            throw BusinessException.build("电话修改错误,请填写正确的联系电话...");
        }

        dto.setUserAge(dto.getUserAge() + YEAR);
        long age = AgeConvertUtil.ageToHour3(dto.getUserAge() + YEAR);
        dto.setUserAgeHour(age);
        dto.setUserAgeUnit("小时");
        String hob = "";
        if (!dto.getHobbies().isEmpty()){
            StringBuilder hobby = new StringBuilder();
            List<String> hobbies = dto.getHobbies();
            for (String s : hobbies) {
                hobby.append(s).append(",");
            }
            if (hobby.length() > 0){
                hob = hobby.substring( 0, hobby.length() - 1);
            }
        }
        dto.setHobbyStr(hob);
        if (StringUtils.isBlank(dto.getUserDescribe())){
            dto.setUserDescribe("hello,留下你的个人介绍吧...");
        }
        //更新明细信息
        boolean result = userDetailOperationMapper.updateUserInfoById(dto);
        if (result){
            SysLogDTO logDto = SingleFactory.getLogDto(SysOperatorEnum.UPDATE_USER_INFO.getDesc()+",账户id:"+dto.getAccountId());
            asyncTask.tranceLog(logDto);

            return ResponseResult.isSuccess(true);
        }
        return ResponseResult.error(null,false);

    }

    @Override
    public CurrentUserInfoVO getCurrentUserInfo(String account,String token) {
        Object userObj = stringRedisTemplate.opsForHash().get(token, account);
        if (ObjectUtils.isEmpty(userObj)){
            throw BusinessException.build("当前用户信息错误,请退出重新登录后修改");
        }
        LoginUserInfoPO user =  JSON.parseObject((String) userObj,LoginUserInfoPO.class);
        LoginUserInfoPO userPo = userOperationMapper.queryUserInfoById(user.getAccountId());

        LoginDetailInfoPO detailPo = userDetailOperationMapper.queryUserDetailInfoById(user.getUserId());

        CurrentUserInfoVO vo = new CurrentUserInfoVO();
        vo.setAccountId(userPo.getAccountId());
        vo.setLoginAccount(userPo.getLoginAccount());
        vo.setLoginValidityDay(userPo.getLoginValidityDay());
        vo.setLoginMode(userPo.getLoginMode());
        vo.setLoginFailedCount(userPo.getLoginFailedCount());
        vo.setAccountState(userPo.getAccountState());
        vo.setLoginState(userPo.getLoginState());
        vo.setStartLoginTime(userPo.getStartLoginTime());
        vo.setRegTime(userPo.getRegTime());
        vo.setUserId(detailPo.getUserId());
        vo.setUserName(detailPo.getUserName());
        vo.setUserSex(detailPo.getUserSex());
        vo.setUserAge(detailPo.getUserAge());
        vo.setUserPhone(detailPo.getUserPhone());
        vo.setUserAddress(detailPo.getUserAddress());
        vo.setHobbies(detailPo.getHobbies());
        vo.setIsVip(detailPo.getIsVip());
        vo.setUserDescribe(detailPo.getUserDescribe());
        vo.setAvatarName(detailPo.getAvatarName());
        return vo;
    }


    @Override
    public ResponseResult<String> updateUserAvatar(MultipartFile file, String fileType, String token) {

        List<String> mediaTypes = Arrays.asList(FileTypeEnum.JPG.getType(),FileTypeEnum.PNG.getType(),FileTypeEnum.WEBP.getType(),FileTypeEnum.JPEG.getType());
        if (!mediaTypes.contains(fileType)){
            return ResponseResult.error(null,"文件类型不符合规范,请重新上传(只支持jpg/png/webp格式)...",StringUtils.EMPTY);
        }
        LoginUserInfoPO loginUserInfo = LoginUserContext.getLoginUserInfoPO();
        if (ObjectUtils.isEmpty(loginUserInfo)){
            throw BusinessException.build("用户登录信息异常,请重新登录...");
        }

        String url = fileUploadServiceFactory.uploadUserAvatar(file,fileType);

        return ResponseResult.isSuccess(url);
    }

    @Override
    public UserArtMainVO getUserArtList(ArticleMainDTO mainDTO) {
        PageHelper.startPage(mainDTO.getPage(),mainDTO.getSize());
        List<UserArtListVO> vos = articleMapper.getUserArtList(mainDTO,LoginUserContext.getLoginUserInfoPO().getUserId());
        PageInfo<UserArtListVO> pageInfo = new PageInfo<>(vos);
        UserArtMainVO vo = new UserArtMainVO();
        vo.setVoList(pageInfo.getList());
        vo.setTotals(pageInfo.getTotal());
        return vo;
    }
}
